import inspect
import random
import threading
import cv2
import numpy as np
import sys
import os

# 添加上级目录到Python路径以导入MvImport模块
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from MvImport.MvCameraControl_class import *
from MvImport.MvErrorDefine_const import *
from MvImport.PixelType_header import *

# 尝试导入halcon，如果不存在则创建模拟函数
try:
import halcon
    HALCON_AVAILABLE = True
except ImportError:
    HALCON_AVAILABLE = False
    print("Warning: Halcon library not found. Creating mock functions.")
    
    # 创建模拟的halcon函数
    class MockHalcon:
        @staticmethod
        def gen_image_interleaved(pointer, format_type, width, height, pitch, pixel_type, orientation, width_img, height_img, start_row, start_column, bits_per_channel, bit_shift):
            # 返回一个模拟的图像对象
            return MockImage()
        
        @staticmethod
        def gen_image1_extern(pixel_type, width, height, pointer, pitch):
            # 返回一个模拟的图像对象
            return MockImage()
    
    class MockImage:
        def __init__(self):
            pass
    
    halcon = MockHalcon()


# 强制关闭线程
def Async_raise(tid, exctype):
    tid = ctypes.c_long(tid)
    if not inspect.isclass(exctype):
        exctype = type(exctype)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")


# 停止线程
def Stop_thread(thread):
    Async_raise(thread.ident, SystemExit)


# 转为16进制字符串
def To_hex_str(num):
    chaDic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'}
    hexStr = ""
    if num < 0:
        num = num + 2 ** 32
    while num >= 16:
        digit = num % 16
        hexStr = chaDic.get(digit, str(digit)) + hexStr
        num //= 16
    hexStr = chaDic.get(num, str(num)) + hexStr
    return hexStr


# 是否是Mono图像
def Is_mono_data(enGvspPixelType):
    if PixelType_Gvsp_Mono8 == enGvspPixelType or PixelType_Gvsp_Mono10 == enGvspPixelType \
            or PixelType_Gvsp_Mono10_Packed == enGvspPixelType or PixelType_Gvsp_Mono12 == enGvspPixelType \
            or PixelType_Gvsp_Mono12_Packed == enGvspPixelType:
        return True
    else:
        return False


# 是否是彩色图像
def Is_color_data(enGvspPixelType):
    if PixelType_Gvsp_BayerGR8 == enGvspPixelType or PixelType_Gvsp_BayerRG8 == enGvspPixelType \
            or PixelType_Gvsp_BayerGB8 == enGvspPixelType or PixelType_Gvsp_BayerBG8 == enGvspPixelType \
            or PixelType_Gvsp_BayerGR10 == enGvspPixelType or PixelType_Gvsp_BayerRG10 == enGvspPixelType \
            or PixelType_Gvsp_BayerGB10 == enGvspPixelType or PixelType_Gvsp_BayerBG10 == enGvspPixelType \
            or PixelType_Gvsp_BayerGR12 == enGvspPixelType or PixelType_Gvsp_BayerRG12 == enGvspPixelType \
            or PixelType_Gvsp_BayerGB12 == enGvspPixelType or PixelType_Gvsp_BayerBG12 == enGvspPixelType \
            or PixelType_Gvsp_BayerGR10_Packed == enGvspPixelType or PixelType_Gvsp_BayerRG10_Packed == enGvspPixelType \
            or PixelType_Gvsp_BayerGB10_Packed == enGvspPixelType or PixelType_Gvsp_BayerBG10_Packed == enGvspPixelType \
            or PixelType_Gvsp_BayerGR12_Packed == enGvspPixelType or PixelType_Gvsp_BayerRG12_Packed == enGvspPixelType \
            or PixelType_Gvsp_BayerGB12_Packed == enGvspPixelType or PixelType_Gvsp_BayerBG12_Packed == enGvspPixelType \
            or PixelType_Gvsp_YUV422_Packed == enGvspPixelType or PixelType_Gvsp_YUV422_YUYV_Packed == enGvspPixelType \
            or PixelType_Gvsp_RGB8_Packed == enGvspPixelType:
        return True
    else:
        return False


# Mono图像转为python数组
def Mono_numpy(data, nWidth, nHeight):
    data_ = np.frombuffer(data, count=int(nWidth * nHeight), dtype=np.uint8, offset=0)
    data_mono_arr = data_.reshape(nHeight, nWidth)
    numArray = np.zeros([nHeight, nWidth, 1], "uint8")
    numArray[:, :, 0] = data_mono_arr
    return numArray


def Mono_numpy2(data, nWidth, nHeight):
    data_ = np.frombuffer(data, count=int(nWidth * nHeight), dtype=np.uint8, offset=0)
    data_mono_arr = data_.reshape(nHeight, nWidth)
    numArray = np.zeros([nHeight, nWidth, 3], "uint8")
    numArray[:, :, 0] = data_mono_arr
    numArray[:, :, 1] = data_mono_arr
    numArray[:, :, 2] = data_mono_arr
    return numArray


# 彩色图像转为python数组
def Color_numpy(data, nWidth, nHeight):
    data_ = np.frombuffer(data, count=int(nWidth * nHeight * 3), dtype=np.uint8, offset=0)
    data_r = data_[0:nWidth * nHeight * 3:3]
    data_g = data_[1:nWidth * nHeight * 3:3]
    data_b = data_[2:nWidth * nHeight * 3:3]

    data_r_arr = data_r.reshape(nHeight, nWidth)
    data_g_arr = data_g.reshape(nHeight, nWidth)
    data_b_arr = data_b.reshape(nHeight, nWidth)
    numArray = np.zeros([nHeight, nWidth, 3], "uint8")

    numArray[:, :, 0] = data_r_arr
    numArray[:, :, 1] = data_g_arr
    numArray[:, :, 2] = data_b_arr
    return numArray


# 需要显示的图像数据转换
def image_control(data, stFrameInfo):
    if stFrameInfo.enPixelType == 17301505:
        # 变成了一个二维数组
        data = data.reshape((stFrameInfo.nHeight, stFrameInfo.nWidth))
        image_show(image=data, name=stFrameInfo.nHeight)
    elif stFrameInfo.enPixelType == 17301514:
        data = data.reshape(stFrameInfo.nHeight, stFrameInfo.nWidth, -1)
        image = cv2.cvtColor(data, cv2.COLOR_BAYER_GB2RGB)
        image_show(image=image, name=stFrameInfo.nHeight)
    elif stFrameInfo.enPixelType == PixelType_Gvsp_RGB8_Packed:
        data = data.reshape(stFrameInfo.nHeight, stFrameInfo.nWidth, -1)
        image = cv2.cvtColor(data, cv2.COLOR_RGB2BGR)
        image_show(image=image, name=stFrameInfo.nHeight)
    # elif stFrameInfo.enPixelType == 34603039:
    elif stFrameInfo.enPixelType == PixelType_Gvsp_YUV422_Packed:
        data = data.reshape(stFrameInfo.nHeight, stFrameInfo.nWidth, -1)
        image = cv2.cvtColor(data, cv2.COLOR_YUV2BGR_Y422)
        image_show(image=image, name=stFrameInfo.nHeight)

# 显示图像
def image_show(image, name):
    image = cv2.resize(image, (int(5472 * 0.2), int(3648 * 0.2)), interpolation=cv2.INTER_AREA)
    name = str(name)
    cv2.imshow(name, image)
    k = cv2.waitKey(1) & 0xFF == ord('q')


# 相机操作类
class CameraOperation:
    # 用于存储所有遍历的相机名称
    devList = []
    deviceInfoList = MV_CC_DEVICE_INFO_LIST()

    def __init__(self, sn):
        self.data = None
        self.image = None
        self.st_frame_info = None
        self.b_thread_closed = False
        self.SN = sn
        self.buf_lock = threading.Lock()
        self.condition = threading.Condition()
        self.b_open_device = False
        self.b_start_grabbing = False
        self.buf_save_image = None
        self.obj_cam = None

    @classmethod
    def enum_devices(cls):
        cls.devList = []
        ret = MvCamera.MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, cls.deviceInfoList)
        if ret != 0:
            strError = "Enum devices fail! ret = :" + To_hex_str(ret)
            print(strError)

        if cls.deviceInfoList.nDeviceNum == 0:
            print("Find %d devices!" % cls.deviceInfoList.nDeviceNum)

        for i in range(0, cls.deviceInfoList.nDeviceNum):
            mvcc_dev_info = cast(cls.deviceInfoList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents

            if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE or mvcc_dev_info.nTLayerType == MV_GENTL_GIGE_DEVICE:
                NameUbytes = mvcc_dev_info.SpecialInfo.stGigEInfo.chUserDefinedName
                GigeSN = bytes(NameUbytes).decode('GBK').strip('\x00')
                if GigeSN != "":
                    cls.devList.append(GigeSN)

            elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE:
                NameUbytes = mvcc_dev_info.SpecialInfo.stUsb3VInfo.chUserDefinedName
                Usb3SN = bytes(NameUbytes).decode('GBK').strip('\x00')
                if Usb3SN != "":
                    cls.devList.append(Usb3SN)

    # 打开相机
    def Open_device(self):
        if not self.b_open_device:
            if self.SN == "":
                return MV_E_CALLORDER

            for i, item in enumerate(self.devList):
                if item == self.SN:
                    stDeviceList = cast(self.deviceInfoList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents
                    self.obj_cam = MvCamera()
                    ret = self.obj_cam.MV_CC_CreateHandle(stDeviceList)
                    if ret != 0:
                        self.obj_cam.MV_CC_DestroyHandle()
                        return ret

                    ret = self.obj_cam.MV_CC_OpenDevice()
                    if ret != 0:
                        return ret
                    print("open device successfully!")
                    self.b_open_device = True
                    self.b_thread_closed = False
                    return MV_OK

    # 开始取图
    def Start_grabbing(self):
        if not self.b_start_grabbing and self.b_open_device:
            ret = self.obj_cam.MV_CC_StartGrabbing()
            if ret != 0:
                return ret
            self.b_start_grabbing = True
            print("start grabbing successfully!")
            try:
                thread_id = random.randint(1, 10000)  # 线程ID
                self.h_thread_handle = threading.Thread(target=self.Work_thread)
                self.h_thread_handle.start()
                self.b_thread_closed = True
            finally:
                pass
            return MV_OK
        return MV_E_CALLORDER

    # 停止取图
    def Stop_grabbing(self):
        if self.b_start_grabbing and self.b_open_device:
            # 退出线程
            if self.b_thread_closed:
                Stop_thread(self.h_thread_handle)
                self.b_thread_closed = False
            ret = self.obj_cam.MV_CC_StopGrabbing()
            if ret != 0:
                return ret
            print("stop grabbing successfully!")
            self.b_start_grabbing = False
            return MV_OK
        else:
            return MV_E_CALLORDER

    # 关闭相机
    def Close_device(self):
        if self.b_open_device:
            # 退出线程
            if self.b_thread_closed:
                Stop_thread(self.h_thread_handle)
                self.b_thread_closed = False
            ret = self.obj_cam.MV_CC_CloseDevice()
            if ret != 0:
                return ret

        # ch:销毁句柄 | Destroy handle
        self.obj_cam.MV_CC_DestroyHandle()
        self.b_open_device = False
        self.b_start_grabbing = False
        print("close device successfully!")
        return MV_OK

    # 设置触发模式
    def Set_trigger_mode(self, is_trigger_mode):
        if not self.b_open_device:
            return MV_E_CALLORDER

        if not is_trigger_mode:
            ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", 0)
            if ret != 0:
                return ret
        else:
            ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", 1)
            if ret != 0:
                return ret
            ret = self.obj_cam.MV_CC_SetEnumValue("TriggerSource", 7)
            if ret != 0:
                return ret

        return MV_OK

    # 软触发一次
    def Trigger_once(self):
        if self.b_open_device:
            ret = self.obj_cam.MV_CC_SetCommandValue("TriggerSoftware")
            if ret != 0:
                strError = "TriggerSoftware failed ret:" + To_hex_str(ret)
                print(strError)
                return False
            with self.condition:
                return self.condition.wait(1)

    # 获取参数
    def Get_parameter(self):
        if self.b_open_device:
            stFloatParam_FrameRate = MVCC_FLOATVALUE()
            memset(byref(stFloatParam_FrameRate), 0, sizeof(MVCC_FLOATVALUE))
            stFloatParam_exposureTime = MVCC_FLOATVALUE()
            memset(byref(stFloatParam_exposureTime), 0, sizeof(MVCC_FLOATVALUE))
            stFloatParam_gain = MVCC_FLOATVALUE()
            memset(byref(stFloatParam_gain), 0, sizeof(MVCC_FLOATVALUE))
            ret = self.obj_cam.MV_CC_GetFloatValue("AcquisitionFrameRate", stFloatParam_FrameRate)
            if ret != 0:
                return ret
            self.frame_rate = stFloatParam_FrameRate.fCurValue

            ret = self.obj_cam.MV_CC_GetFloatValue("ExposureTime", stFloatParam_exposureTime)
            if ret != 0:
                return ret
            self.exposure_time = stFloatParam_exposureTime.fCurValue

            ret = self.obj_cam.MV_CC_GetFloatValue("Gain", stFloatParam_gain)
            if ret != 0:
                return ret
            self.gain = stFloatParam_gain.fCurValue

            return MV_OK

    # 设置参数
    def Set_parameter(self, frameRate, exposureTime, gain):
        if '' == frameRate or '' == exposureTime or '' == gain:
            print('show info', 'please type in the text box !')
            return MV_E_PARAMETER
        if self.b_open_device:
            ret = self.obj_cam.MV_CC_SetFloatValue("ExposureTime", float(exposureTime))
            if ret != 0:
                print('show error', 'set exposure time fail! ret = ' + To_hex_str(ret))
                return ret

            ret = self.obj_cam.MV_CC_SetFloatValue("Gain", float(gain))
            if ret != 0:
                print('show error', 'set gain fail! ret = ' + To_hex_str(ret))
                return ret

            ret = self.obj_cam.MV_CC_SetFloatValue("AcquisitionFrameRate", float(frameRate))
            if ret != 0:
                print('show error', 'set acquistion frame rate fail! ret = ' + To_hex_str(ret))
                return ret

            print('show info', 'set parameter success!')

            return MV_OK

    # 取图线程函数
    # def Work_thread(self):
    #     stOutFrame = MV_FRAME_OUT()
    #     memset(byref(stOutFrame), 0, sizeof(stOutFrame))
    #
    #     while self.b_start_grabbing:
    #         ret = self.obj_cam.MV_CC_GetImageBuffer(stOutFrame, 1000)
    #         if 0 == ret:
    #             # 拷贝图像和图像信息
    #             if self.buf_save_image is None:
    #                 self.buf_save_image = (c_ubyte * stOutFrame.stFrameInfo.nFrameLen)()
    #             self.st_frame_info = stOutFrame.stFrameInfo
    #
    #             self.buf_lock.acquire()
    #             cdll.msvcrt.memcpy(byref(self.buf_save_image), stOutFrame.pBufAddr, self.st_frame_info.nFrameLen)
    #             self.buf_lock.release()
    #
    #             # 释放缓存
    #             self.obj_cam.MV_CC_FreeImageBuffer(stOutFrame)
    #         else:
    #             continue
    #
    #         if Is_color_data(self.st_frame_info.enPixelType):
    #             with self.condition:
    #                 # 转成nparray类型
    #                 self.data = Color_numpy(self.buf_save_image, self.st_frame_info.nWidth, self.st_frame_info.nHeight)
    #                 self.condition.notify()
    #         if Is_mono_data(self.st_frame_info.enPixelType):
    #             with self.condition:
    #                 self.data = Mono_numpy2(self.buf_save_image, self.st_frame_info.nWidth, self.st_frame_info.nHeight)
    #                 self.condition.notify()

    # 取图线程函数

    def Work_thread(self):
        stOutFrame = MV_FRAME_OUT()
        # 清空stOutFrame
        memset(byref(stOutFrame), 0, sizeof(stOutFrame))

        while self.b_start_grabbing:
            ret = self.obj_cam.MV_CC_GetImageBuffer(stOutFrame, 1000)
            if 0 == ret:
                # 拷贝图像和图像信息
                if self.buf_save_image is None:
                    self.buf_save_image = (c_ubyte * stOutFrame.stFrameInfo.nFrameLen)()
                self.st_frame_info = stOutFrame.stFrameInfo

                self.buf_lock.acquire()
                cdll.msvcrt.memcpy(byref(self.buf_save_image), stOutFrame.pBufAddr, self.st_frame_info.nFrameLen)
                self.buf_lock.release()
                # 释放缓存
                self.obj_cam.MV_CC_FreeImageBuffer(stOutFrame)
                # 返回C实例内部缓冲区的地址 通过监控可知byref 十六进制和addressof的值一样
                pixel_pointer = addressof(self.buf_save_image)
            else:
                continue

            if Is_color_data(self.st_frame_info.enPixelType):
                with self.condition:
                    self.image = halcon.gen_image_interleaved(pixel_pointer, "rgb", self.st_frame_info.nWidth,
                                                              self.st_frame_info.nHeight,
                                                              -1, "byte", 0, 0, 0, 0, -1, 0)
                    self.condition.notify()
            if Is_mono_data(self.st_frame_info.enPixelType):
                with self.condition:
                    self.image = halcon.gen_image1_extern("byte", self.st_frame_info.nWidth, self.st_frame_info.nHeight,
                                                          pixel_pointer, 0)
                    self.condition.notify()

    # def Work_thread(self):
    #     stParam = MVCC_INTVALUE_EX()
    #     memset(byref(stParam), 0, sizeof(MVCC_INTVALUE_EX))
    #     ret = self.obj_cam.MV_CC_GetIntValueEx("PayloadSize", stParam)
    #     if ret != 0:
    #         print("get payload size fail! ret[0x%x]" % ret)
    #         sys.exit()
    #     nDataSize = stParam.nCurValue
    #     pData = (c_ubyte * nDataSize)()
    #     stFrameInfo = MV_FRAME_OUT_INFO_EX()
    #     memset(byref(stFrameInfo), 0, sizeof(stFrameInfo))
    #
    #     while self.b_start_grabbing:
    #         if 0 == ret:
    #             # 拷贝图像和图像信息
    #             ret = self.obj_cam.MV_CC_GetOneFrameTimeout(pData, nDataSize, stFrameInfo, 1000)
    #         else:
    #             continue
    #
    #         # 转成halcon类型
    #         with self.condition:
    #             self.image = halcon.gen_image1_extern("byte", stFrameInfo.nWidth, stFrameInfo.nHeight, id(pData), 0)
    #             self.condition.notify()

    # 转成YOLOV8需要的np.ndarray类型

    # 存jpg图像
    def Save_jpg(self):
        if self.buf_save_image is None:
            return

        # 获取缓存锁
        self.buf_lock.acquire()

        file_path = str(self.st_frame_info.nFrameNum) + ".jpg"

        stSaveParam = MV_SAVE_IMG_TO_FILE_PARAM()
        stSaveParam.enPixelType = self.st_frame_info.enPixelType  # ch:相机对应的像素格式 | en:Camera pixel type
        stSaveParam.nWidth = self.st_frame_info.nWidth  # ch:相机对应的宽 | en:Width
        stSaveParam.nHeight = self.st_frame_info.nHeight  # ch:相机对应的高 | en:Height
        stSaveParam.nDataLen = self.st_frame_info.nFrameLen
        stSaveParam.pData = cast(self.buf_save_image, POINTER(c_ubyte))
        stSaveParam.enImageType = MV_Image_Jpeg  # ch:需要保存的图像类型 | en:Image format to save
        stSaveParam.nQuality = 80
        stSaveParam.pImagePath = file_path.encode('ascii')
        stSaveParam.iMethodValue = 2
        ret = self.obj_cam.MV_CC_SaveImageToFile(stSaveParam)

        self.buf_lock.release()
        return ret

    # 存BMP图像
    def Save_Bmp(self):

        if 0 == self.buf_save_image:
            return

        # 获取缓存锁
        self.buf_lock.acquire()

        file_path = str(self.st_frame_info.nFrameNum) + ".bmp"

        stSaveParam = MV_SAVE_IMG_TO_FILE_PARAM()
        stSaveParam.enPixelType = self.st_frame_info.enPixelType  # ch:相机对应的像素格式 | en:Camera pixel type
        stSaveParam.nWidth = self.st_frame_info.nWidth  # ch:相机对应的宽 | en:Width
        stSaveParam.nHeight = self.st_frame_info.nHeight  # ch:相机对应的高 | en:Height
        stSaveParam.nDataLen = self.st_frame_info.nFrameLen
        stSaveParam.pData = cast(self.buf_save_image, POINTER(c_ubyte))
        stSaveParam.enImageType = MV_Image_Bmp  # ch:需要保存的图像类型 | en:Image format to save
        stSaveParam.nQuality = 8
        stSaveParam.pImagePath = file_path.encode('ascii')
        stSaveParam.iMethodValue = 2
        ret = self.obj_cam.MV_CC_SaveImageToFile(stSaveParam)

        self.buf_lock.release()

        return ret
